
module SmsRb

	#
	# Responsible for construction a geom figures from faces
	#
    class GeomBuilder
        M_PI = Math::PI
        M_PI_2 = M_PI * 2; # obsolete
        M_2_PI = 2 * M_PI;
        M_PI_BY_2 = M_PI / 2;
        
        def initialize
		    # stack of transform matrices, the last transformation is used for construction
            @tr_stack = [Geom::Transformation.new];
			
			# determines quality of round surfaces
            @slice_count = 24
        end
        
        def slices
            return @slice_count
        end
        def slices= val
            @slice_count = val
        end
        
        #--------------------------------------------------
        #---- TRANSFORMATIONS -----------------------------
        #--------------------------------------------------
    
        def transformation=(tr)
            @tr_stack[-1] = tr;
        end

        def transformation
            return @tr_stack.last
        end
        
        def push_transformation(tr)
            @tr_stack.push(tr)
        end
        
        def pop_transformation
            return @tr_stack.pop
        end
        
        #--------------------------------------------------
        #---- MAKING SHAPES METHORDS ----------------------
        #--------------------------------------------------
    
        #
		# Constructs a box and adds it to the given Entities object. 
		# Returns array of constructed faces. 
		# Arguments in inches. 
		#
		def MakeBox(entities, x, y, z, dx, dy, dz)
            retval = [];

            pts = [];
            pts << [x + dx, y + dy, z + dz];
            pts << [x + dx, y - dy, z + dz];
            pts << [x + dx, y - dy, z - dz];
            pts << [x + dx, y + dy, z - dz];
            retval << entities.add_face(pts);

            pts = [];
            pts << [x - dx, y + dy, z + dz];
            pts << [x - dx, y - dy, z + dz];
            pts << [x - dx, y - dy, z - dz];
            pts << [x - dx, y + dy, z - dz];
            retval << entities.add_face(pts);

            pts = [];
            pts << [x + dx, y + dy, z + dz];
            pts << [x - dx, y + dy, z + dz];
            pts << [x - dx, y + dy, z - dz];
            pts << [x + dx, y + dy, z - dz];
            retval << entities.add_face(pts);
            
            pts = [];
            pts << [x + dx, y - dy, z + dz];
            pts << [x - dx, y - dy, z + dz];
            pts << [x - dx, y - dy, z - dz];
            pts << [x + dx, y - dy, z - dz];
            retval << entities.add_face(pts);

            pts = [];
            pts << [x + dx, y + dy, z + dz];
            pts << [x - dx, y + dy, z + dz];
            pts << [x - dx, y - dy, z + dz];
            pts << [x + dx, y - dy, z + dz];
            retval << entities.add_face(pts);
            
            pts = [];
            pts << [x + dx, y + dy, z - dz];
            pts << [x - dx, y + dy, z - dz];
            pts << [x - dx, y - dy, z - dz];
            pts << [x + dx, y - dy, z - dz];
            
            pts = [];
            pts << [x + dx, y + dy, z - dz];
            pts << [x - dx, y + dy, z - dz];
            pts << [x - dx, y - dy, z - dz];
            pts << [x + dx, y - dy, z - dz];
            retval << entities.add_face(pts);
            
            return retval;

            pts = [];
            pts << Geom::Point3d.new(x + dx, y, z + dz);
            pts << Geom::Point3d.new(x - dx, y, z + dz);
            pts << Geom::Point3d.new(x - dx, y, z - dz);
            pts << Geom::Point3d.new(x + dx, y, z - dz);
            retval << entities.add_face(pts);

            pts = [];
            pts << Geom::Point3d.new(x + dx, y + dy, z);
            pts << Geom::Point3d.new(x - dx, y + dy, z);
            pts << Geom::Point3d.new(x - dx, y - dy, z);
            pts << Geom::Point3d.new(x + dx, y - dy, z);
            retval << entities.add_face(pts);
            
            return retval;
        end
        
        #-----------------------------------------
        
		#
		# Constructs a sphere and adds it to the given Entities object. 
		# Note: it depends on a current transformation (last in the stack)
		# that the constructed figure is a sphere or an ellipsoid.
		# Radius equals to 1 inch. 
		#
        def MakeSphere(entities)
            MakeSector(entities, 0, 2.0 * Math::PI)
        end

        #-----------------------------------------
        
		#
		# Constructs a sector and adds it to the given Entities object. 
		# Radius equals to 1 inch.  
		#
		# Arguments: 
		#   phimin, phimax in radians. 
		#
        def MakeSector(entities, phimin = 0, phimax = 2.0 * Math::PI)
            step = M_2_PI / slices();
            count = ((phimax - phimin) / (2 * step)).round
            count *= 2
            step = (phimax - phimin) / count
            
            phi = phimin;
            phimax = phimax - step / 2;
            thetamax = M_PI_BY_2 - step / 2;
            loop {
                break if !(phi < phimax);
                
                x1 = Math.cos(phi);
                y1 = Math.sin(phi);
                x2 = Math.cos(phi + step);
                y2 = Math.sin(phi + step);
                
                theta = 0;
                loop {
                    break if !(theta < thetamax)
                    
                    z1 = Math.sin(theta);
                    r1 = Math.cos(theta);
                    z2 = Math.sin(theta + step);
                    r2 = Math.cos(theta + step);
                    
                    pts = [];
                    pts << [x1 * r1, y1 * r1, z1];
                    pts << [x2 * r1, y2 * r1, z1];
                    pts << [x2 * r2, y2 * r2, z2];
                    pts << [x1 * r2, y1 * r2, z2];
                    MakeFace(entities, pts);
                    
                    pts = [];
                    pts << [x1 * r1, y1 * r1, -z1];
                    pts << [x2 * r1, y2 * r1, -z1];
                    pts << [x2 * r2, y2 * r2, -z2];
                    pts << [x1 * r2, y1 * r2, -z2];
                    MakeFace(entities, pts);
                    
                    theta = theta + step;
                }
                
                phi = phi + step;
            }
        end

		#
		# Constructs a cylinder and add it to the given Entities object. 
		# Arguments: 
		#	bidir 	Constructs the cylinder up and down Z-axis relative to 
		#           the origin of coordinates if it is true. 
		#
        def MakeCylinder(entities, bidir)
            f1 = [];
            f2 = [];
            zz = (bidir) ? -1 : 0;
            step = M_2_PI / slices();
            phi = 0;
            phimax = M_PI_2 - step / 2;
            thetamax = M_PI - step / 2;
            
            loop {
                break if !(phi < phimax);
                
                x1 = Math.cos(phi);
                y1 = Math.sin(phi);
                x2 = Math.cos(phi + step);
                y2 = Math.sin(phi + step);
                
                f1 << [x1, y1, 1];
                f2 << [x1, y1, zz];
                    
                pts = [];
                pts << [x1, y1, 1];
                pts << [x2, y2, 1];
                pts << [x2, y2, zz];
                pts << [x1, y1, zz];
                MakeFace(entities, pts);

                phi = phi + step;
            }

            MakeFace(entities, f1);
            MakeFace(entities, f2);

        end
        
        #--------------------------------------------------
        
		#
		# Constructs a so-called tube and adds it to the given Entities object. 
		# The tube is constructed along segments (p11, p21) and (p12, p22). Bases of 
		# the tube are ellipses. The focal radius of the bases are equal. 
		# Electromagnetic field between two wires can be represented as a tube. 
		# 
		# Arguments:
		#   p11, p12        Foci of the first base, Geom::Point3d objects.
		#   p21, p22        Foci of the second base, Geom::Point3d objects.
		#   bend_plane1     The cutting plane of the first tube's end. Has type Array[point : Point3d, normal : Point3d].
		#   bend_plane2     The cutting plane of the second tube's end.
		#
        def MakeTube(entities, p11, p12, p21, p22, fradius, bend_plane1 = nil, bend_plane2 = nil)
            if fradius <= 0
                return
            end 
            
            #centers of ellipses
            center1 = Geom::Point3d.new((p11.x + p12.x) / 2.0, (p11.y + p12.y) / 2.0, (p11.z + p12.z) / 2.0);
            center2 = Geom::Point3d.new((p21.x + p22.x) / 2.0, (p21.y + p22.y) / 2.0, (p21.z + p22.z) / 2.0);
            
            #compute transformation for 1st ellipse
            yaxis1 = (p12 - p11).normalize();
            zaxis1 = (center2 - center1).normalize();
            xaxis1 = yaxis1.cross(zaxis1);
            tr1 = Geom::Transformation.new(center1, xaxis1, yaxis1);
            len1 = (p12 - p11).length();
            a1 = len1 / 2.0 + fradius
            b1 = Math.sqrt(len1 * fradius + fradius * fradius)
            tr1 = tr1 * Geom::Transformation.scaling(b1, a1, 1.0);
            
            #compute transformation for 2nd ellipse
            yaxis2 = (p22 - p21).normalize();
            zaxis2 = (center1 - center2).normalize();
            xaxis2 = zaxis2.cross(yaxis2);
            tr2 = Geom::Transformation.new(center2, xaxis2, yaxis2);
            len2 = (p22 - p21).length();
            a2 = len2 / 2.0 + fradius
            b2 = Math.sqrt(len2 * fradius + fradius * fradius)
            tr2 = tr2 * Geom::Transformation.scaling(b2, a2, 1.0);
            
            #draw a tube
            step = M_2_PI / slices();
            phi = 0;
            phimax = M_2_PI - step / 2.0;
            
            loop {
                break if !(phi < phimax);
                
                x1 = Math.sin(phi);
                y1 = Math.cos(phi);
                x2 = Math.sin(phi + step);
                y2 = Math.cos(phi + step);
                
                pts1 = [];
                pts1 << tr1 * Geom::Point3d.new(x1, y1, 0);
                pts1 << tr2 * Geom::Point3d.new(x1, y1, 0);
                pts1 << tr2 * Geom::Point3d.new(x2, y2, 0);
                
                pts2 = [];
                pts2 << tr2 * Geom::Point3d.new(x2, y2, 0);
                pts2 << tr1 * Geom::Point3d.new(x2, y2, 0);
                pts2 << tr1 * Geom::Point3d.new(x1, y1, 0);
                
                if !bend_plane1.nil?
                    pts1 = Intersect(pts1, bend_plane1)
                    pts2 = Intersect(pts2, bend_plane1)
                end
                if !bend_plane2.nil?
                    pts1 = Intersect(pts1, bend_plane2)
                    pts2 = Intersect(pts2, bend_plane2)
                end
                
                if (pts1.size >= 3)
                    MakeFace(entities, pts1);
                end
                if (pts2.size >= 3)
                    MakeFace(entities, pts2);
                end
                phi = phi + step;
            }
        end
        
        #----------------------------------------------
        
        #
        # Returns a new polygon cut by a plane. 
        #
        # Arguments: 
        #   polygon     Loop of Geom::Point3d objects.
        #   plane       Array[point : Point3d, normal: Point3d].
        #
        def Intersect(polygon, plane)
            newpolygon = []
            n = polygon.size
            (0..n-1).each do |i|
                p1 = polygon[i];
                p2 = polygon[(i+1)%n];
                
                pr1 = p1.project_to_plane(plane);
                pr2 = p2.project_to_plane(plane);
                
                vpr1 = p1 - pr1;
                vpr2 = p2 - pr2;
                
                
                dot1 = vpr1.length == 0 ? 1 : plane[1].dot(vpr1.normalize);
                dot2 = vpr2.length == 0 ? 1 : plane[1].dot(vpr2.normalize);
                
                if dot1 >= 0 and dot2 >= 0 #keep both points
                    newpolygon << p1;
                elsif dot1 >= 0 and dot2 < 0 #
                    newpolygon << p1;
                    newp = _intersect_plane_line(p1, p2, plane)
                    newpolygon << newp if !newp.nil?
                elsif dot1 < 0 and dot2 >= 0 
                    newp = _intersect_plane_line(p1, p2, plane)
                    newpolygon << newp if !newp.nil?
                else #ignore both points
                end
            end
            
            if (newpolygon.size < 3)
                newpolygon = [];
            end
            return newpolygon
        end
        
        #--------------------------------------------------
        
        #
        # Constructs a so-called sausage and adds it to the given Entities object. A sausage is 
        # constructed from a set of tubes fitted together. 
        #
        # Arguments (in inches): 
        #   points  Array of pairs of points. Each pair describes a base of a tube (see GeomBuilder.MakeTube). 
        #           Number of pairs equals to number of tubes plus 1. Have type Array[Point3d, Point3d]. 
        #   fradius Focal radius of a tube's base. 
        #
        def MakeSausage(entities, points, fradius)
            if (points.nil? or points.size < 2 or fradius <= 0)
                return;
            end
             
            p11 = points[0][0]
            p12 = points[0][1]
            p21 = points[1][0]
            p22 = points[1][1]
            _draw_sector(entities, p21, p22, p11, p12, p21, p22, fradius);
            
            bend_plane1 = nil;
            (0..points.size-2).each do |i| 
                p11 = points[i][0];
                p12 = points[i][1];
                p21 = points[i+1][0];
                p22 = points[i+1][1];
                p31 = nil;
                p32 = nil;
                
                #find bend planes
                bend_plane2 = nil;
                if i < points.size-2
                    p31 = points[i+2][0];
                    p32 = points[i+2][1];
                    bend_plane2 = _find_bend_plane(p11, p12, p21, p22, p31, p32)
                end
                
                #draw tube
                push_transformation(Geom::Transformation.new)
                MakeTube(entities, p11, p12, p21, p22, fradius, bend_plane1, bend_plane2)
                pop_transformation()
                
                if !bend_plane2.nil?
                    bend_plane2[1].reverse!
                end
                bend_plane1 = bend_plane2;
                
                #draw sector
                if (i == points.size-2)
                    p31 = p11
                    p32 = p12
                end
                _draw_sector(entities, p11, p12, p21, p22, p31, p32, fradius);
            end
        end
        
        #--------------------------------------------------
        
        #
        # Constructs a prism and adds it to the given Entities object. 
        #
        # Arguments (in inches): 
        #   pts1    Array of Point3d objects that represents a base of the prism. 
        #
        def MakePrism(entities, pts1, height)
            
            pts2 = []
            (0..pts1.size-1).each do |i|
                p = pts1[i].clone
                p.z += height
                pts2 << p
            end
            
            MakeFace(entities, pts1);
            MakeFace(entities, pts2);
            
            n = pts1.size()
            (0..n-1).each { |i|
                j = (i + 1) % n
                pts = []
                pts << pts1[i] << pts1[j] << pts2[j] << pts2[i]
                MakeFace(entities, pts)
            };
            
        end
        
        #--------------------------------------------------
        
        #
        # Constructs a face and adds it to the given Entities object.  
        #
        # Arguments: 
        #   points  Array of Point3d objects that lie on the same plane. 
        #
        # Returns the Face object if successful.
        #
        def MakeFace(entities, points)
            retval = nil;
            pts = [];
            begin
                points.each { |pt|
                    pt1 = transformation * pt;
                    exist = false;
                    pts.each { |pt2| exist = true if (pt1.distance(pt2) < 1e-3);}
                    pts << pt1 if (!exist);
                }
                retval = entities.add_face(pts);
            rescue StandardError => err
                #puts "MakeFace():" + err
            end
            return retval
        end
        
        #--------------------------------------------------
        #---- MATERIAL AND DISPLAYING METHODS -------------
        #--------------------------------------------------

        #
        # Creates a material if not exists. 
        #
        # Arguments: 
        #   color   Object of the Color class. 
        #   alpha   The opacity of the material. The value must be between 0 and 1. 
        #           A value of 0 means that the material is completely transparent. 
        #
        # 
        # Returns the requested Material object. 
        #
        def GeomBuilder.CreateMaterial(name, color, alpha) # 0xbbggrr, 0.25
            mm = Sketchup.active_model.materials;
            if (!mm[name])
                mm.add(name);
            end
            m = mm[name];
            m.color = color;
            m.alpha = alpha;
            return m;
        end
        
        #
        # Sets the material for each face of the entity. 
        # Arguments: 
        #   entity  Face, ComponentDefinition, Entities or Array of objects of mentioned types. 
        #
        def GeomBuilder.SetMaterial(entity, name, *args)
            if (args.length == 2)
                GeomBuilder.CreateMaterial(name, args[0], args[1]);
            end

            if (entity.class == Sketchup::Face)
                m = Sketchup.active_model.materials[name];
                entity.material = m;
                entity.back_material = m;

            elsif (entity.class == Sketchup::ComponentDefinition)
                GeomBuilder.SetMaterial(entity.entities, name);
                
            elsif (entity.class == Sketchup::Entities)
                entity.each { |e| GeomBuilder.SetMaterial(e, name); }

            elsif (entity.class == Array)
                entity.each { |e| GeomBuilder.SetMaterial(e, name); }
            end;
        end
        
        #
        # Hides each face of the entity
        #
        def GeomBuilder.HideEdges(entity)
            if (entity.class == Sketchup::Edge)
                entity.visible = false;
            elsif (entity.class == Sketchup::Face)
                entity.edges.each { |e| GeomBuilder.HideEdges(e); }
            elsif (entity.class == Sketchup::Entities)
                entity.each { |e| GeomBuilder.HideEdges(e); }
            elsif (entity.class == Array)
                entity.each { |e| GeomBuilder.HideEdges(e); }
            end
        end
        
        #------------------------------------------------#
        #          PRIVATE MEMBERS                       #
        #------------------------------------------------#
        private
        
        def _find_bend_plane(p11, p12, p21, p22, p31, p32)
            center1 = Geom::Point3d.new((p11.x + p12.x) / 2.0, (p11.y + p12.y) / 2.0, (p11.z + p12.z) / 2.0)
            center2 = Geom::Point3d.new((p21.x + p22.x) / 2.0, (p21.y + p22.y) / 2.0, (p21.z + p22.z) / 2.0)
            center3 = Geom::Point3d.new((p31.x + p32.x) / 2.0, (p31.y + p32.y) / 2.0, (p31.z + p32.z) / 2.0)
            
            v1 = center1 - center2
            v2 = center3 - center2
            v3 = v2.cross(p21 - p22)
            v4 = v1.cross(p22 - p21)
            v1.normalize!
            v2.normalize!
            v3.normalize!
            v4.normalize!
            
            sv = (v3 + v4)
            sv.normalize!()
            if sv.length == 0 then sv = v4.cross(p21 - p22); sv.normalize!; end;
            
            mv = (p22 - p21)
            mv.normalize!()
            normal = sv.cross(mv);
            
            if normal.dot(v1) < 0
                normal.reverse!
            end
            return [center2, normal]
        end
        
        # line-plane intersection. 
        # 
        # Return crosspoint 
        def _intersect_plane_line(p1, p2, plane)
            pr_p = [0, 0, 0].project_to_plane(plane);
            m_distance = [0, 0, 0].distance(pr_p);
            m_normal = (pr_p - [0, 0, 0]).normalize;
            # Check if the line is parallel to the plane.
            a_ray_direction = (Geom::Vector3d.new(p2 - p1)).normalize;
            a_ray_origin = Geom::Vector3d.new(p1.x, p1.y, p1.z);
            
            denom = m_normal.dot(a_ray_direction);
            if(denom == 0.0) 
                return nil;
            end
            #t is the distance from ray origin to plane.
            
            t = (m_distance - m_normal.dot(a_ray_origin)) / denom;
            
            tmp = a_ray_direction;
            tmp.length = t;
            v = a_ray_origin + tmp;
            return Geom::Point3d.new(v.to_a);
        end
        
        def _draw_sector(entities, p11, p12, p21, p22, p31, p32, fradius)
            center1 = Geom::Point3d.new((p11.x + p12.x) / 2.0, (p11.y + p12.y) / 2.0, (p11.z + p12.z) / 2.0);
            center2 = Geom::Point3d.new((p21.x + p22.x) / 2.0, (p21.y + p22.y) / 2.0, (p21.z + p22.z) / 2.0);
            center3 = Geom::Point3d.new((p31.x + p32.x) / 2.0, (p31.y + p32.y) / 2.0, (p31.z + p32.z) / 2.0);
            
            zaxis1 = (p21 - p22).normalize
            yaxis1 = (zaxis1.cross(center1 - center2)).normalize()
            xaxis1 = (yaxis1.cross(zaxis1)).normalize
            
            yaxis2 = zaxis1.cross(center3 - center2).normalize
            
            yaxis3 = (yaxis1 + yaxis2).reverse().normalize()
            if (yaxis3.length == 0) then yaxis3 = yaxis1; end;
            xaxis3 = yaxis3.cross(zaxis1).normalize()
            sector_tr = Geom::Transformation.new(center2, xaxis3, yaxis3)
            
            len = (p22 - p21).length()
            a = len / 2.0 + fradius
            b = Math.sqrt(len * fradius + fradius * fradius)
            sector_tr = sector_tr * Geom::Transformation.scaling(b, b, a)
            
            dphi = Math::PI - yaxis1.angle_between(yaxis2)
            
            push_transformation(sector_tr)
            MakeSector(entities, -dphi / 2.0, +dphi / 2.0);
            pop_transformation()
        end
        
    end

end

